# WebSocket 服务 以下内容仅针对于编程爱好者,普通用户可忽略。 # 概述 Websocket是一种基于HTTP的实时通信协议。[参考阮一峰的Websocket教程](https://www.ruanyifeng.com/blog/2017/05/websocket.html)
Quicker 的 Websocket 服务提供了一个实时通信接口,方便使用者通过自己编写的HTML5网页或者小程序、APP等与Quicker通信,实现特定需求。 功能演示:TODO - [示例HTML客户端源代码](https://github.com/cuiliang/tools.getquicker.cn/blob/master/tools.getquicker.cn/wwwroot/static/ws.html) - [示例客户端网页](https://tools.getquicker.cn/static/ws.html) Websocket客户端通过局域网直接连接的方式与Quicker通信,相对于推送服务连接方式,不需要经过服务器中转,网络延迟小(可实现类似于触摸板的鼠标控制),可用于传送较大量的数据(如传送文件),也可主动向客户端发送数据。只是websocket接口不能直接通过公网地址访问,使用这个协议也需要一定的专业知识。 # 设置 在设置窗口 -> 手机APP/WebSocket设置页面中,开启WebSocket服务,并根据需要修改默认端口和验证码。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/272392/1645095919321-6a80ead6-781c-499c-b481-95f5e654e886.png#averageHue=%23f6f5f5&clientId=u73efa793-8886-4&from=paste&height=325&id=u7d244b72&originHeight=852&originWidth=1195&originalType=binary&ratio=1&rotation=0&showTitle=false&size=109513&status=done&style=none&taskId=uf60c5989-9e4c-42f5-a5fd-b000c8de71f&title=&width=455.23809523809524)
参数:
**启用Websocket服务: **是否开启服务。
**端口:** 服务端口号,为1-65535之间的数字。 - 端口号不能其他网络服务冲突。 - 您需要将Quicker加入到Windows防火墙白名单或者在防火墙设置中开启此端口的访问权限。 **验证码:** 建立Websocket连接后,经过验证码比对以后才可以进一步通信。可以避免在同一个网络中有多位Quicker用户时连错电脑。
**启用安全连接(wss):** 是否启用`wss`连接。类似于`https`相对于`http`,`wss`相对于`ws`是一种加密的安全连接方式。 ## 如何建立安全连接 在浏览器中,以`http`方式访问的网页中只能建立非加密websocket连接(ws),以`https`方式访问的网页中只能建立加密的weboskcet连接(wss)。如果以 http 方式访问网页,一些 Javascript 脚本的权限会受到限制(如读取或写入剪贴板、访问摄像头等),为避免这些问题的出现,建议您使用 https 方式访问网页,同时以安全连接 wss 方式建立websocket连接。 使用安全连接方式时,您可以使用`wss://转换后的本机ip地址.lan.quicker.cc:端口号/ws`访问Quicker的Websocket服务(末尾的ws表示服务网址路径)。其中ip地址部分为您电脑的局域网ip地址中的点`.`替换为英文短横线`-`后的字符串。如电脑的局域网ip地址为`192.168.1.56`时,对应的websocket服务URI为:`wss://192-168-1-56.lan.quicker.cc:668/ws`。 注:这里的域名只是ip地址的别名。就像不同的局域网会有相同的`192.168.*.*`的内网地址,您并不能通过这个域名访问到别人局域网里的ip,别人也不能通过这个域名访问到您的电脑。(域名解析是需要联网的,除非您在内网架设域名解析服务) 当不使用安全连接时,可以使用`ws://ip地址:端口/ws`的方式访问Quicker的Websocket服务,如`ws://192.168.1.56:668/ws`。 **内置的web服务**
如果您编写了客户端网页,可以放置在`我的文档\Quicker\_websocket\`文件夹下,即可通过与websocket相同端口的网址访问(放置后需重启Quicker或websocket服务),如`https://192-168-1-56.lan.quicker.cc:668/index.html`。 # 通信协议 通过文本格式向Quicker发送请求并获取响应。请求和响应都使用Json格式,消息参数与推送服务接近。 **服务端和客户端:** - 常开服务等待连接的是服务端,这里就是Quicker软件本体了。 - 主动发起连接请求的网页、APP、小程序等。 **通信过程:** - 客户端发起Websocket连接。 - 如果设置了验证码,客户端首先发送验证码消息。 - 返回验证结果。 - 如果未设置验证码,服务端(Quicker软件)直接发送验证成功消息。 - 连接建立。 - 双方根据需要发送消息和返回结果。 ## 请求地址 非安全连接时:`ws://电脑ip地址:设置的端口号/ws`
安全连接时:`wss://192-168-1-156.lan.quicker.cc/ws` 客户 js 示例代码: ```javascript let ip = txtIp.value; let port = txtPort.value; let _password = txtPassword.value; let uri = ''; let protocol = ''; // 根据网页是否是https连接,构造连接地址的组成部分 if (isHttps) { protocol = 'wss'; var ipstr = ip.replaceAll('.', '-'); // 将ip地址中的.替换为- uri = `${ipstr}.lan.quicker.cc:${port}`; } else { protocol = 'ws'; uri = `${ip}:${port}`; } // 构造完整连接地址 uri = `${protocol}://${uri}/ws`; // 建立连接 try { socket = new WebSocket(uri); } catch (e) { console.log('connect to websocket failed.', e); showError(e.message); return; } setStateContent('连接中...'); ``` 同时,客户端开始监听websocket的各种事件: - open:连接建立 - error:发生错误 - close:连接断开 - message:收到消息 ## 消息结构 **常规请求消息** ```json { messageType: 消息类型常量, serial: 消息编号, operation: '操作类型,如copy将data参数内容写入剪贴板', data: '数据,为文本,也可能为对象', action: '操作类型为action时指定执行的动作名称或id', extData: '可选的额外数据,文本或对象', wait: 是否等待操作返回结果 } ``` 参数在不必要的时候可以省略。 **常规响应消息** ```json { messageType: 消息类型常量, serial: 消息编号, replyTo: 响应的请求消息编号, isSuccess: 操作是否成功, message: 操作失败时的提示消息, data: 可选的返回数据(文本或对象), extData: 可选的额外返回数据(文本或对象) } ``` **消息类型常量**
对应于消息中`messageType`参数的取值。 - 2:命令请求消息,用于发送操作指令和内容。 - 4:命令响应消息,返回指令操作结果。 - 5:身份验证请求,客户端发送验证码。 - 6:身份验证响应,返回密码是否正确。 ## 身份验证 连接建立后,开始身份验证流程。
如果Quicker中未设置连接验证码(仅在家庭等安全环境中使用),则直接下发验证通过消息。 ```json // Quicker 发送给客户端的验证通过消息 { messageType: 6, isSuccess: true, // isSuccess表示是否验证通过 replyTo: 0 } ``` 如果设置了连接验证码,则需要客户端首先发送身份验证消息,服务端(Quicker软件)返回验证结果。
客户端示例代码(连接建立后主动发送验证请求消息): ```javascript // 监听websocket连接事件,连接后如果设置了密码则发送请求消息 socket.addEventListener('open', function(event) { setStateContent('已连接,待认证'); // 设置了密码,要发送密码消息 if (_password) { sendMsg({ messageType: 5, // 消息类型为5 data: _password, // data中放置验证密码 serial: getSerial() }); } }); ``` Quicker收到消息后对比验证码,返回结果。 ```json { messageType: 6, // 验证响应消息类型 isSuccess: false, // isSuccess表示是否验证通过 replyTo: 1, message: '验证码不正确' } ``` ## 上行消息 这里指客户端发起请求,Quicker进行执行和响应的消息。
大部分与[推送服务](https://getquicker.net/kc/manual/doc/connection)中支持的请求类型一致,只额外增加了传送文件支持。 ### 请求消息 ```json { "messageType":2, "serial": 1000, "operation":"paste", "data":"Hello Quicker!Quicker真好玩!哈哈😄", "action":"动作名或ID", "wait":false } ``` 参数说明 - messageType:消息类型标识,请求消息为2. - serial:请求编号(不强制编号,可以直接写0)。 - **operation**: 操作类型,请参考推送服务文档。除推送中的操作类型,另外支持sendfile(传送文件)、pasteimage(粘贴图片到当前窗口)操作,详见后续章节说明。 - **data**:操作参数数据。 - action:操作类型为action时,指定动作的id或名称。请参考推送请求消息格式。 - wait:是否等待动作响应。 ### 向Quicker传输文件 相对于推送服务,websocket直连不受带宽限制,可以用于传送文件(其他设备传送到Quicker)。具体协议如下: - 通过两条消息传送文件。 - 第一条:文本消息,告知下一步要传送文件,以及文件的名称。 - 第二条:二进制消息,发送文件内容。 文本消息格式: ```json { "messageType":2, "serial": 1000, "operation":"sendfile", // sendfile 表示要传送文件 "data":"文件名.png" // 文件名 } ``` 二进制消息:为文件内容字节数组。js参考代码: ```javascript // 发送文件 function sendFile() { var file = document.getElementById('theFile').files[0]; if (!file) { console.log('没有文件。'); return } if (file.size > 200000000) { alert('File should be smaller than 200MB') return } var filename = file.name; console.log('file name:', filename); // 发送第一条消息,传送文件名 var msg = { messageType: 2, operation: 'sendfile', serial: msgSerial++, data: filename }; socket.send(JSON.stringify(msg)); // 发送第二条:二进制消息,传输文件数据 var reader = new FileReader(); var rawData = new ArrayBuffer(); reader.loadend = function () { } reader.onload = function (e) { rawData = e.target.result; socket.send(rawData); console.log("文件已发送"); } reader.readAsArrayBuffer(file); } ``` 响应消息格式 ```json { "MessageType":4, "ReplyTo":0, "IsSuccess":true, "Data":"D:\\Docs\\Quicker\\_recv\\20220113_021127521_quicker.bin", "Message":"ok" } ``` 参数: - MessageType,固定为4. - ReplyTo:响应的哪一条消息的Serial值。 - IsSuccess:是否成功响应。 - Data:返回的数据。 - Message:错误消息。 ## 下行消息 Quicker组合动作增加了 Websocket 模块,可用于向连接的客户端发送消息。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/272392/1645107799581-3cb85515-b5bd-485d-a83f-e6bde2abfaa1.png#averageHue=%23fcfcfc&clientId=u73efa793-8886-4&from=paste&height=322&id=u6bcdf27a&originHeight=846&originWidth=1257&originalType=binary&ratio=1&rotation=0&showTitle=false&size=61784&status=done&style=none&taskId=ubfeebfd0-389c-4e84-9f34-d7a5dbc036e&title=&width=478.85714285714283) - 向客户端发送文本消息:按规定的消息格式发送json文本内容。这里也可以通过表达式创建匿名对象,Quicker会自动序列化为文本。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/272392/1645107898183-02a23ffd-9fa4-427e-af54-9b22370e6697.png#averageHue=%23fcfbfb&clientId=u73efa793-8886-4&from=paste&height=121&id=ueea0ebf7&originHeight=318&originWidth=890&originalType=binary&ratio=1&rotation=0&showTitle=false&size=26860&status=done&style=none&taskId=u72744d24-5626-41d1-bc7f-d0275b2cc0c&title=&width=339.04761904761904) - 向客户端发送文件(二进制方式):使用与“上行消息”中说明的两个消息一致的方式发送文件。(第一个消息发送文件名,第二个消息为二进制方式发送文件内容)。这种方式在iOS的浏览器中不支持。 - 向客户端发送文件(Base64方式):将文件内容序列化为base64字符串后放入extData参数中一起发送。可支持iOS,只是传送的数据略大一些。 客户端消息处理参考代码: ```javascript var msg = JSON.parse(data); // 密码验证结果消息,MSG_AUTH_RESP = 6 if (msg.messageType == MSG_AUTH_RESP) { if (msg.isSuccess) { setStateContent('已连接'); document.getElementById('tests').classList.remove('d-none'); } else { setStateContent('认证失败'); } } else if (msg.messageType === MSG_PUSH) { // MSG_PUSH = 2,表示常规命令消息 if (msg.operation === 'sendfile') { if (!msg.extData) { // 消息中没有extData,表示是通过二进制方式发送的文件内容, // 需要等下一个消息接收二进制数据 _nextFileName = msg.data; console.log('next file name:', _nextFileName); } else { // 有extData,它是文件内容的base64转换。 let blob = base64toBlob(msg.extData); SaveBlobAs(blob, msg.data); // msg.data 为文件名。 } } else if (msg.operation == 'copy') { // copy 命令,将data内容写入剪贴板。 document.getElementById('txtData').value = msg.data; writeClipboard(msg.data); } } ```